#ifdef _RMS_TOML_

#include "clientthr.h"


#include <stdio.h>
#include <time.h>
#include "cTypes.h"

#define SetVector(vec, ptr, size) vec.resize(size); memcpy(&(vec[0]), ptr, size)

// globals
CAuthClient* pClient;

int gMeasure;
CRITICAL_SECTION r1CS;
bool r1CSinitialized = false;

void CAuthClient::Create()
{
  int i;
  prevErr = esNotConnected; errStatus = esNoError;
_log("Starting...\n");
  srand(time(0));

  Terminated=false;
  InitializeCriticalSection(&termCS);
  if(!r1CSinitialized)
  {
    InitializeCriticalSection(&r1CS);
    r1CSinitialized=true;
  }
  hDone=CreateEventHandle(NULL, FALSE, FALSE, NULL );
  SetEvent(hDone);// no pending threads
// prepare RC4-generator
  RC4Key k;
  string tmp = strtime() + UserLogin + UserPassword;
  tmp.copy((char*)k.data, 254);
  k.length = tmp.size();
  *(WORD*) &k.data[k.length] = rand();
  k.length+=2;

  pRC4 = new RC4(k);


// generate Session ID
  SessionID.resize(8);

  for(i=0; i<8; i++) SessionID[i] = pRC4->GetByte();
// set Client ID
  ClientID.resize(32);
  memset(&ClientID[0], 0, 32);
  memcpy(&ClientID[0], UserLogin.c_str(), UserLogin.size());
  memcpy(&ClientID[16], UserPassword.c_str(), UserPassword.size());
// set CheckWord
  tmp = "nfozzba14k4a9dk4"; // must be 16 bytes length
  CheckWord.resize(16);
  memcpy(&CheckWord[0], tmp.c_str(), tmp.length());
// set the public key
  pMKey = new CInteger(36, string("242hbs3si8f0ndy0va785nankesjpqmdxdi1t7f2ft1nt3t8tnfozzba14k4a9dk4xd7ww1pzzuo65afnxfg6kc01n8onrs5h9u44zpckr652i94z22j2mrhdmzowyl35hxp321mr4psli3gx87huciqxnztxo670aijc3v6he5ownmlwxtm7uvbijizrikvu5sqmkd"));
  pEKey = new CInteger(36, string("1ekh"));

  udp.Start(*pServ);

  bool ready = false;

  ResetEvent(hDone);
  LastPingSend = AddTime(GetNowTime(),-TimeBetweenPongs),
  LastPongReceived = AddTime(GetNowTime(), TimeBetweenPongs+RetryTime*10);
  hThread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)IncomingMgr, this, 0, 0);
  SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST);
  hThread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)OutcomingMgr, this, 0, 0);
};


CAuthClient::~CAuthClient()
{
  errStatus = esTerminating;
  Terminate();

  delete pServ;
  delete pRC4;
  delete pMKey;
  delete pEKey;
//  DeleteCriticalSection(&r1CS);
  DeleteCriticalSection(&termCS);
};

bool CAuthClient::IsNotTerminated()
{
  EnterCriticalSection(&termCS);
  bool res = !Terminated;
  LeaveCriticalSection(&termCS);
  return res;
};

void CAuthClient::Terminate()
{
  EnterCriticalSection(&termCS);
  bool t = Terminated;
  Terminated = true;
  LeaveCriticalSection(&termCS);
  if(t) return;
  if(hThread)
  {
    SetEvent(SystemTimerEvent);
    WaitForSingleObject(hDone, INFINITE);
    SetEvent(hDone);
  }
};


DWORD WINAPI CAuthClient::IncomingMgr(void* lpv)
{
  CAuthClient* Self = (CAuthClient*) lpv;
  CUDPClient* udp = &Self->udp;

  BYTE buf[128];

  bool logged = false;

EnterCriticalSection(&r1CS);
  Self->RetryCount=Self->Accepted=0;
  Self->WaitingForPong=false;
  gMeasure=0;
LeaveCriticalSection(&r1CS);
//  LastPongReceived = AddTime(GetNowTime(),-TimeBetweenPongs*3);

/* time:
  FILETIME LastPongReceived;

*/


// Send authentication request:
// prepare auth-data

// special messages:
//  CNetworkMessage authmsg(*Self->pServ, cmdLogon, buf, 16);
//  CNetworkMessage pongmsg(*Self->pServ, cmdPong, buf, 0);
  CNetworkMessage logoffmsg(*Self->pServ, cmdLogoff, buf, 0);
  Self->Status=0; 

// send
_log(strtime() << "Logging on to the server [" << Self->pServ->GetTextAddrPort() << "]...\n");
//  udp->SendMsg(&authmsg);
  
// main cycle:
  while( (Self->IsNotTerminated()) && (Self->RetryCount<=MaxRetryCount) && udp->IsNotTerminated())
  {
//_log("inc gmes = " << gMeasure << "\n");
//_D("["<<Self->RetryCount<<"]\n");
    if(udp->MessagesCount()>0)
    {
      CNetworkMessage msg = udp->PopMessage();
      if(msg.DataLen<sizeof(Command)) continue;
      Command cmd = *(Command*) &msg.Data[0];
      switch(cmd)
      {
/*        case cmdAccept:
        {
          Self->Status=1; // accepted
          if(msg.DataLen==sizeof(Command)*2)
          {
            cmd = *(Command*) &msg.Data[sizeof(Command)];
_log(strtime() << INT2STR(cmd) << " accepted.\n");
            switch(cmd)
            {
              default:
              {
                break;
              }
            }

          } else continue;
          break;
        }*/
        case cmdARespond:
        {
          if(!Self->WaitingForPong)
          {
//            _D(strtime()<<"WARNING: Unexpected respond from server.\n");
            continue;
          }
          // decode message
          BYTE respond[RSABlockSize+1];

          if( msg.DataLen != sizeof(Command) + RSABlockSize + 1)
          {
            _D(strtime()<<"WARNING: The message length is "<< msg.DataLen << "bytes instead of "<< sizeof(Command) + RSABlockSize + 1 << ".\n");
            continue; // m.b. print warning
          }
          
          CInteger inp(&msg.Data[sizeof(Command)], RSABlockSize+1 );
          CInteger res;
          res.powmod(inp, *Self->pEKey, *Self->pMKey);

          if(res.GetData(respond, RSABlockSize)==RSABlockSize)
          {

          // 1. parse data
// 128 >= random2(32bytes) + random1(24bytes) +SessionID(8bytes) + checkword(16bytes) + zeroes/coeffs(48 bytes)
            vector<BYTE> r2, r1, sID, cw;

            int c = 0, d;
            d=32; SetVector(r2, &respond[c], d); c+=d;
            d=24; SetVector(r1, &respond[c], d); c+=d;
            d=8; SetVector(sID, &respond[c], d); c+=d;
            d=16; SetVector(cw,  &respond[c], d); c+=d;
            // 2 Check if respond is correct
            if( (Self->CheckWord!=cw) || (!Self->CheckRandom1(r1, Self->RetryCount))) // probably someone tries to forge the server
            {
//  int x;
//  printf("\nRANDOM: %02x", r1[0]);for(x=1; x<4; x++) printf("-%02x", r1[x]);

              _log(strtime() << "WARNING: incorrect respond from server.\n");
EnterCriticalSection(&r1CS);
              Self->RetryCount+=(int)(MaxRetryCount*1.5);
              Self->Accepted = 0;
LeaveCriticalSection(&r1CS);
              continue;
            }
            if(sID!=Self->SessionID)
            {
              _log(strtime() << "WARNING: session ID mismatch.\n");
              //RetryCount++;
              continue;
            }
            _D(strtime() << "Access granted.\n");
            Self->Accepted = 1;

            Self->LastPongReceived = GetNowTime();
EnterCriticalSection(&r1CS);
            Self->WaitingForPong=false;
            Self->RetryCount^=Self->RetryCount; // clear rc
            gMeasure=0;
LeaveCriticalSection(&r1CS);
            break;
          }

          _log(strtime()<<"ERROR: Incorrect respond from server.\n");
          Self->Accepted = 0;
          continue;

        }
        case cmdError:
        {
          if(msg.DataLen==sizeof(Command)+sizeof(Errors))
          {
            Self->Status=-1; //error
            Errors er = *(Errors*) &msg.Data[sizeof(Command)];
_log(strtime() << "Error: Server reports error (" <<INT2STR(er) << ")\n");
            Self->RetryCount++;
          } else continue;
          break;
        }
        default:
        {
_log(strtime()+"Server reports: <" << INT2STR(cmd) << ">\n");
          break;
        }
      }
    } else
    {
      if (!Self->IsNotTerminated()) break;
      Sleep(500);
      if (!Self->IsNotTerminated()) break;
    }

  }
  Self->Accepted = 0;

  if(Self->RetryCount>MaxRetryCount)
  {
    Self->errStatus = esNoData;
_log(strtime() << "ERROR: No respond from server.\n");
//_log("nodata="<<NoData<<" nodatatimeout="<<NoDataTimeOut<<"\n");
    udp->SendMsg(&logoffmsg);
  }
  if(!udp->IsNotTerminated())
  {
    Self->errStatus = esNetworkFailed;
_log(strtime() << udp->GetErrorText() << "\n");
  }

  if( (Self->RetryCount<=MaxRetryCount)&&(udp->IsNotTerminated()))
    udp->SendMsg(&logoffmsg);

  Self->errStatus = esTerminating;
_log("Terminating...\n");
  udp->Terminate();

  Self->errStatus = esTerminated;

  Self->Terminate();
  //  _endthread();
  SetEvent(Self->hDone);
  Self->hThread=0;
//  _log("[19] ");
  return 0;
};


DWORD WINAPI CAuthClient::OutcomingMgr(void* lpv)
{
  CAuthClient* Self = (CAuthClient*) lpv;
  CUDPClient* udp = &Self->udp;
  BYTE msg[RSABlockSize+1];
  int c,d,i, meas = (Self->pRC4->GetByte()+Self->pRC4->GetByte()+Self->pRC4->GetByte()*Self->pRC4->GetByte())*gMeasure;
  c=0;
c=24; //d=24; memcpy(&msg[c], &Self->random1[0], d); c+=d;

EnterCriticalSection(&r1CS);
  for(i=0; i<MaxRetryCount; i++)
    Self->random1[i].resize(24);
LeaveCriticalSection(&r1CS);

  d=8;  memcpy(&msg[c], &Self->SessionID[0], d); c+=d;
  d=32; memcpy(&msg[c], &Self->ClientID[0], d); c+=d;
  d=16; memcpy(&msg[c], &Self->CheckWord[0], d); c+=d;
  d=4;  memcpy(&msg[c], &Self->cType, d); c+=d;
  d=4;  memcpy(&msg[c], &meas, d); c+=d;

  memset(&msg[c], 0, RSABlockSize - c); // fill the rest with zeroes
  CInteger infmsg((BYTE*)&msg[0], RSABlockSize);
  CInteger tosend((BYTE*)&msg[0], RSABlockSize+1);
  BYTE TheData[RSABlockSize+1+sizeof(Command)];
  *((Command*)TheData) = cmdARequest;
  while( (Self->IsNotTerminated()) && udp->IsNotTerminated())
  {
    Sleep(200);
    FILETIME now = GetNowTime();
    if( DiffTime(now, Self->LastPingSend)>RetryTime/* *(RetryCount+1)*/ )
    {
/*
      >RetryTime   WaitingForPong    NeedSend
      0            0                 0
      0            1                 0
      1            0                 wait 30 sec, send
      1            1                 inc retry count, send
*/

      if(Self->WaitingForPong)
      {
        EnterCriticalSection(&r1CS);
        Self->RetryCount++;
        LeaveCriticalSection(&r1CS);
      } else
      if(DiffTime(now, Self->LastPingSend)<TimeBetweenPongs)
      {
//        Sleep(500);
        continue;
      }

      // prepare new message
      // keep: SessionID, ClientID, CheckWord, cType
      // change: random1
      if(Self->RetryCount>=MaxRetryCount) continue;
      for(i=0; i<24; i++)
        Self->random1[Self->RetryCount][i] = Self->pRC4->GetByte();
      // copy data:
      meas = gMeasure;
      memcpy(&msg[0], &Self->random1[Self->RetryCount][0], 24);
      memcpy(&msg[84], &meas, 4);
      infmsg.Load((BYTE*)&msg[0], RSABlockSize); // information message serialized
      tosend.powmod(infmsg, *Self->pEKey, *Self->pMKey); // the message encoded with the public key
  
      tosend.GetData((BYTE*)&TheData[sizeof(Command)], RSABlockSize+1); // prepare the buffer to send
      CNetworkMessage TheMessage(*Self->pServ, TheData, RSABlockSize +1 + sizeof(Command));
      Self->udp.SendMsg(&TheMessage);
      Self->LastPingSend = GetNowTime();
      EnterCriticalSection(&r1CS);
      Self->WaitingForPong = true;
      LeaveCriticalSection(&r1CS);
    }
  }
  return 0;
}

bool CAuthClient::CheckRandom1(const vector<BYTE> &r1, int rc)
{
  int i, M = rc;
  if(M>=MaxRetryCount) M = MaxRetryCount-1;
  for(i=0; i<=M; i++)
    if(r1 == random1[i]) return true;
  return false;
}

string CAuthClient::GetErrorStatusText()
{
  if(prevErr==errStatus) return "-";
  prevErr = errStatus;
  string s;
  switch(errStatus)
  {
    case esNoError:
    {
      s = "OK";
      break;
    }
    case esConnecting:
    {
      s = "Connecting...";
      break;
    }
    case esStillConnecting:
    {
      s = "Still connecting...";
      break;
    }
    case esConnected:
    {
      s = "Connected";
      break;
    }
    case esNotConnected:
    {
      s = "Not Connected.";
      break;
    }
    case esNetworkFailed:
    {
      s = "Error: Network failure";
      break;
    }
    case esNoData:
    {
      s = "No data from server";
      break;
    }
    case esTerminating:
    {
      s = "Closing connection...";
      break;
    }
    case esUnknownError:
    {
      s = "Unknown error!";
      break;
    }
    default:
    {
      s = "---";
      break;
    }
  }
  return s;
}

#endif